<?php

namespace controller;

header('Content-type: text/html; charset=UTF-8');
define('IA_ROOT', str_replace('\\', DIRECTORY_SEPARATOR, __DIR__));

// 类自动加载器
require_once IA_ROOT . '/autoload.php';

class Mvc
{
    private static $_config;
    private static $_subdir;
    private static $_modules;
    private static $_startTime;

    // 启动计时器
    public function __construct()
    {
        self::$_startTime = microtime(true);
    }

    // 配置信息
    private function config()
    {
        if (self::$_config == null) {
            self::$_config = require IA_ROOT . '/config.php';
        }
    }

    // 获取子目录
    private function subdir()
    {
        if (self::$_subdir == null) {
            $_subdir = parse_url(htmlentities($_SERVER['REQUEST_URI']), PHP_URL_PATH);
            if ($_subdir && substr($_SERVER['REQUEST_URI'], 0, 2) !== '//' && substr($_subdir, 0, 1) === '/' && false === strpos($_subdir, '//')) {
                while ($_subdir !== '/') {
                    if (false !== strpos(str_replace('\\', '/', IA_ROOT), rtrim($_subdir, '/'))) break;
                    $_subdir = str_replace('\\', '/', dirname($_subdir));
                }
                $_subdir = rtrim($_subdir, '/');
            } else {
                $_subdir = false;
            }
            self::$_subdir = $_subdir;
        }
    }

    // 模块名称
    private function modules()
    {
        if (self::$_modules == null) {
            $_modules = [];
            foreach (glob(IA_ROOT . '/' . __NAMESPACE__ . '/*') as $_source) {
                if (is_dir($_source)) $_modules[] = basename($_source);
            }
            self::$_modules = $_modules;
        }
    }

    // 设置时区
    private function timezone()
    {
        date_default_timezone_set(isset(self::$_config['timezone']) ? self::$_config['timezone'] : 'Asia/Shanghai');
    }

    // Debug信息
    private function debug()
    {
        if (isset(self::$_config['debug']) && self::$_config['debug']) {
            error_reporting(E_ALL);
            ini_set('display_errors', '1');
            ini_set('error_log', IA_ROOT . '/error_log.txt');
            // 注册自定义错误处理器
            set_error_handler(function ($errno, $errstr, $errfile, $errline) {
                // 忽略缓存文件不存在的警告
                if ($errno === E_WARNING && strpos($errstr, 'unlink') !== false && strpos($errstr, 'No such file or directory') !== false) {
                    return true;
                }
                // 记录其他错误
                $types = [
                    E_ERROR => 'Error',
                    E_WARNING => 'Warning',
                    E_PARSE => 'Parse Error',
                    E_NOTICE => 'Notice',
                    E_CORE_ERROR => 'Core Error',
                    E_CORE_WARNING => 'Core Warning',
                    E_COMPILE_ERROR => 'Compile Error',
                    E_COMPILE_WARNING => 'Compile Warning',
                    E_USER_ERROR => 'User Error',
                    E_USER_WARNING => 'User Warning',
                    E_USER_NOTICE => 'User Notice',
                    E_STRICT => 'Strict Notice',
                    E_RECOVERABLE_ERROR => 'Recoverable Error',
                    E_DEPRECATED => 'Deprecated',
                    E_USER_DEPRECATED => 'User Deprecated'
                ];
                error_log(sprintf("[%s] PHP %s: %s in %s on line %d", date('d-M-Y H:i:s'), isset($types[$errno]) ? $types[$errno] : 'Unknown Error', $errstr, $errfile, $errline));
                return true;
            });
        }
    }

    // 数据库零查询静态缓存
    private function cache()
    {
        $path_url = htmlentities(explode('?', $_SERVER['REQUEST_URI'])[0]);
        // 不缓存后台、api和搜索页面
        if (isset(self::$_config['cache']) && self::$_config['cache'] && !preg_match('/(\/[\w]+)?(\/admin\/|\/api\/|\/search[\.\/])/', $path_url)) {
            $file_name = md5("http" . ((isset($_SERVER["HTTPS"]) && in_array(strtolower($_SERVER["HTTPS"]), ["on", "1"])) || (isset($_SERVER["SERVER_PORT"]) && "443" == $_SERVER["SERVER_PORT"]) ? "s" : "") . "://{$_SERVER["HTTP_HOST"]}{$path_url}");
            $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
            // GET请求缓存，缓存前端GET请求页面
            if ($_SERVER['REQUEST_METHOD'] == 'GET') {
                // 如果缓存文件存在且没有过期
                if (file_exists($cache_file) && filesize($cache_file) > 10 && time() - filemtime($cache_file) < self::$_config['cache']) {
                    include $cache_file;
                    exit();
                }
                ob_start([$this, 'auto_cache']);
            } else {
                if (file_exists($cache_file)) {
                    @unlink($cache_file);
                }
            }
        }
    }

    // 自动缓存
    private function auto_cache($contents)
    {
        $path_url = htmlentities(explode('?', $_SERVER['REQUEST_URI'])[0]);
        // 不缓存404页面
        if (!preg_match('#<title>404[^<>]*not found</title>#', $contents)) {
            $file_name = md5("http" . ((isset($_SERVER["HTTPS"]) && in_array(strtolower($_SERVER["HTTPS"]), ["on", "1"])) || (isset($_SERVER["SERVER_PORT"]) && "443" == $_SERVER["SERVER_PORT"]) ? "s" : "") . "://{$_SERVER["HTTP_HOST"]}{$path_url}");
            // 根据md5前两位把缓存文件分散开，避免文件过多
            $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
            $cache_path = dirname($cache_file);
            // 递归创建并且不缓存404页面
            if (!is_dir($cache_path)) mkdir($cache_path, 0700, true);
            $tmp_file = tempnam(dirname($cache_file), 'cache_tmp_');
            $tmp_content = "<?php" . PHP_EOL;
            $tmp_content .= "defined('IA_ROOT') || exit();" . PHP_EOL;
            if (preg_match('/(\/[\w]+)?(\/api\/)/', $path_url)) {
                $tmp_content .= "header('Content-type: application/json; charset=UTF-8');" . PHP_EOL;
                $tmp_content .= "header('Access-Control-Allow-Origin: *');" . PHP_EOL;
            }
            $tmp_content .= "?>" . PHP_EOL;
            $tmp_content .= $contents;
            if ($tmp_file && file_put_contents($tmp_file, $tmp_content)) {
                if (rename($tmp_file, $cache_file)) {
                    chmod($cache_file, 0700);
                } else {
                    @unlink($tmp_file);
                }
            }
            // 每次清理最多3个目录，避免性能影响
            $sub_dirs = glob(IA_ROOT . '/mvc/tpl/*', GLOB_ONLYDIR);
            $select_dirs = [];
            if (!empty($sub_dirs)) {
                shuffle($sub_dirs);
                $select_dirs = array_slice($sub_dirs, 0, min(3, count($sub_dirs)));
            }
            foreach ($select_dirs as $dir) {
                $files = glob($dir . '/*.php');
                foreach ($files as $file) {
                    if (is_file($file) && time() - filemtime($file) > self::$_config['cache']) {
                        @unlink($file);
                    }
                }
            }
        }
        return $contents;
    }

    // 派发路由到控制器
    private function dispatch()
    {
        $pathInfo = substr(parse_url(htmlentities($_SERVER['REQUEST_URI']), PHP_URL_PATH) ?: '', strlen(self::$_subdir ?: ''));
        $routeDispatch = false;
        if (isset(self::$_config['route']) && self::$_config['route']) {
            // 强制路由模式
            $module = strstr(ltrim($pathInfo, '/'), '/', true);
            if (!in_array($module, self::$_modules)) $module = 'index';
            $requestMethod = 'get';
            if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
                $requestMethod = 'ajax';
            } elseif (isset($_SERVER['REQUEST_METHOD'])) {
                $requestMethod = strtolower($_SERVER['REQUEST_METHOD']);
            }
            if (false !== self::$_subdir && isset(self::$_config['rules'][$module][$requestMethod])) {
                foreach ((array)self::$_config['rules'][$module][$requestMethod] as $rule => $_controller) {
                    $matches = [];
                    if ($rule === $pathInfo || (substr_count($rule, '/') === substr_count($pathInfo, '/') && false !== strpos($rule, '(') && false !== strpos($rule, ')') && preg_match('#' . str_replace('#', '\\#', $rule) . '#', $pathInfo, $matches))) {
                        $routeDispatch = true;
                        // 解析出路由参数
                        $params = array_slice($matches, 1);
                        // 解析出控制器
                        $controller = __NAMESPACE__ . '\\' . $module . '\\' . str_replace('/', '\\', dirname($_controller));
                        // 解析出方法
                        $method = basename($_controller);
                        // 传参调用控制器中的方法
                        if (class_exists($controller) && method_exists($controller, $method)) {
                            $routeDispatch = true;
                            call_user_func_array([new $controller(), $method], $params);
                        } else {
                            $routeDispatch = false;
                        }
                        break;
                    }
                }
            }
        } else {
            // 非强制路由，分割 path info 并过滤空值数组
            $params = array_filter(explode('/', $pathInfo));
            // 解析出控制器
            $controller = __NAMESPACE__ . '\\' . ucfirst(preg_replace('/\.html/i', '', array_shift($params)) ?: 'Index') . 'Controller';
            // 解析出方法
            list($method,) = explode('.', array_shift($params));
            // 传参调用控制器中的方法
            if (false !== self::$_subdir && is_file(str_replace('\\', '/', $controller) . '.php') && class_exists($controller) && method_exists($controller, $method)) {
                $routeDispatch = true;
                call_user_func_array([new $controller(), $method ?: 'Index'], $params);
            } else {
                $routeDispatch = false;
            }
        }
        // 路由派发失败，显示404页面
        if (!$routeDispatch) {
            header("HTTP/1.0 404 Not Found");
            include IA_ROOT . '/view/404.html';
        }
    }

    // 页面执行完成时间日志
    private function finished()
    {
        if (isset(self::$_config['debug']) && self::$_config['debug']) {
            $time = round((microtime(true) - self::$_startTime) * 1000, 2);
            $log = sprintf("[%s] Request processed in %s ms: %s", date('d-M-Y H:i:s'), $time, $_SERVER['REQUEST_URI']);
            error_log($log);
        }
    }

    public function run()
    {
        $this->config();
        $this->subdir();
        $this->modules();
        $this->timezone();
        $this->debug();
        $this->cache();
        $this->dispatch();
        $this->finished();
    }
}
(new Mvc())->run();
